Estructura System.Span<T>

En este artículo se proporcionan comentarios adicionales a la documentación de referencia de esta API.

El Span<T> tipo es una estructura de referencia que se asigna en la pila en lugar de en el montón administrado. Los tipos de estructura de referencia tienen una serie de restricciones para asegurarse de que no se pueden promover al montón administrado, incluidos los que no se pueden boxear, no se pueden asignar a variables de tipo Objecto dynamic a ningún tipo de interfaz, no pueden ser campos de un tipo de referencia y no se pueden usar entre await los límites y yield . Además, las llamadas a dos métodos Equals(Object) y GetHashCode, inician una NotSupportedExceptionexcepción .

Importante

Dado que es un tipo de solo pila, Span<T> no es adecuado para muchos escenarios que requieren almacenar referencias a búferes en el montón. Esto es cierto, por ejemplo, de rutinas que realizan llamadas de método asincrónico. En estos escenarios, puede usar los tipos y System.ReadOnlyMemory<T> complementariosSystem.Memory<T>.

Para los intervalos que representan estructuras inmutables o de solo lectura, use System.ReadOnlySpan<T>.

Memoria

Span<T> representa una región contigua de memoria arbitraria. A menudo se usa una Span<T> instancia para contener los elementos de una matriz o una parte de una matriz. A diferencia de una matriz, sin embargo, una Span<T> instancia puede apuntar a la memoria administrada, la memoria nativa o la memoria administrada en la pila. En el ejemplo siguiente se crea un Span<Byte> objeto a partir de una matriz:

// Create a span over an array.
var array = new byte[100];
var arraySpan = new Span<byte>(array);

byte data = 0;
for (int ctr = 0; ctr < arraySpan.Length; ctr++)
    arraySpan[ctr] = data++;

int arraySum = 0;
foreach (var value in array)
    arraySum += value;

Console.WriteLine($"The sum is {arraySum}");
// Output:  The sum is 4950
// Create a span over an array.
let array = Array.zeroCreate<byte> 100
let arraySpan = Span<byte> array

let mutable data = 0uy
for i = 0 to arraySpan.Length - 1 do
    arraySpan[i] <- data
    data <- data + 1uy

let mutable arraySum = 0
for value in array do
    arraySum <- arraySum + int value

printfn $"The sum is {arraySum}"
// Output:  The sum is 4950

En el ejemplo siguiente se crea un a Span<Byte> partir de 100 bytes de memoria nativa:

// Create a span from native memory.
var native = Marshal.AllocHGlobal(100);
Span<byte> nativeSpan;
unsafe
{
    nativeSpan = new Span<byte>(native.ToPointer(), 100);
}
byte data = 0;
for (int ctr = 0; ctr < nativeSpan.Length; ctr++)
    nativeSpan[ctr] = data++;

int nativeSum = 0;
foreach (var value in nativeSpan)
    nativeSum += value;

Console.WriteLine($"The sum is {nativeSum}");
Marshal.FreeHGlobal(native);
// Output:  The sum is 4950
// Create a span from native memory.
let native = Marshal.AllocHGlobal 100
let nativeSpan = Span<byte>(native.ToPointer(), 100)

let mutable data = 0uy
for i = 0 to nativeSpan.Length - 1 do
    nativeSpan[i] <- data
    data <- data + 1uy

let mutable nativeSum = 0
for value in nativeSpan do
    nativeSum <- nativeSum + int value

printfn $"The sum is {nativeSum}"
Marshal.FreeHGlobal native
// Output:  The sum is 4950

En el ejemplo siguiente se usa la palabra clave stackalloc de C# para asignar 100 bytes de memoria en la pila:

// Create a span on the stack.
byte data = 0;
Span<byte> stackSpan = stackalloc byte[100];
for (int ctr = 0; ctr < stackSpan.Length; ctr++)
    stackSpan[ctr] = data++;

int stackSum = 0;
foreach (var value in stackSpan)
    stackSum += value;

Console.WriteLine($"The sum is {stackSum}");
// Output:  The sum is 4950
    // Create a span on the stack.
    let mutable data = 0uy
    let stackSpan = 
        let p = NativeInterop.NativePtr.stackalloc<byte> 100 |> NativeInterop.NativePtr.toVoidPtr
        Span<byte>(p, 100)

    for i = 0 to stackSpan.Length - 1 do
        stackSpan[i] <- data
        data <- data + 1uy

    let mutable stackSum = 0
    for value in stackSpan do
        stackSum <- stackSum + int value

    printfn $"The sum is {stackSum}"
// Output:  The sum is 4950

Dado que Span<T> es una abstracción sobre un bloque arbitrario de memoria, los Span<T> métodos del tipo y los métodos con Span<T> parámetros funcionan en cualquier Span<T> objeto independientemente del tipo de memoria que encapsula. Por ejemplo, cada una de las secciones independientes del código que inicializan el intervalo y calculan la suma de sus elementos se pueden cambiar en métodos de inicialización y cálculo únicos, como se muestra en el ejemplo siguiente:

public static void WorkWithSpans()
{
    // Create a span over an array.
    var array = new byte[100];
    var arraySpan = new Span<byte>(array);

    InitializeSpan(arraySpan);
    Console.WriteLine($"The sum is {ComputeSum(arraySpan):N0}");

    // Create an array from native memory.
    var native = Marshal.AllocHGlobal(100);
    Span<byte> nativeSpan;
    unsafe
    {
        nativeSpan = new Span<byte>(native.ToPointer(), 100);
    }

    InitializeSpan(nativeSpan);
    Console.WriteLine($"The sum is {ComputeSum(nativeSpan):N0}");

    Marshal.FreeHGlobal(native);

    // Create a span on the stack.
    Span<byte> stackSpan = stackalloc byte[100];

    InitializeSpan(stackSpan);
    Console.WriteLine($"The sum is {ComputeSum(stackSpan):N0}");
}

public static void InitializeSpan(Span<byte> span)
{
    byte value = 0;
    for (int ctr = 0; ctr < span.Length; ctr++)
        span[ctr] = value++;
}

public static int ComputeSum(Span<byte> span)
{
    int sum = 0;
    foreach (var value in span)
        sum += value;

    return sum;
}
// The example displays the following output:
//    The sum is 4,950
//    The sum is 4,950
//    The sum is 4,950
open System
open System.Runtime.InteropServices
open FSharp.NativeInterop

// Package FSharp.NativeInterop.NativePtr.stackalloc for reuse.
let inline stackalloc<'a when 'a: unmanaged> length : Span<'a> =
    let voidPointer = NativePtr.stackalloc<'a> length |> NativePtr.toVoidPtr
    Span<'a>(voidPointer, length)

let initializeSpan (span: Span<byte>) =
    let mutable value = 0uy
    for i = 0 to span.Length - 1 do
        span[i] <- value
        value <- value + 1uy

let computeSum (span: Span<byte>) =
    let mutable sum = 0
    for value in span do
        sum <- sum + int value
    sum

let workWithSpans () =
    // Create a span over an array.
    let array = Array.zeroCreate<byte> 100
    let arraySpan = Span<byte> array

    initializeSpan arraySpan
    printfn $"The sum is {computeSum arraySpan:N0}"

    // Create an array from native memory.
    let native = Marshal.AllocHGlobal 100
    let nativeSpan = Span<byte>(native.ToPointer(), 100)

    initializeSpan nativeSpan
    printfn $"The sum is {computeSum nativeSpan:N0}"

    Marshal.FreeHGlobal native

    // Create a span on the stack.
    let stackSpan = stackalloc 100

    initializeSpan stackSpan
    printfn $"The sum is {computeSum stackSpan:N0}"

// The example displays the following output:
//    The sum is 4,950
//    The sum is 4,950
//    The sum is 4,950

Matrices

Cuando encapsula una matriz, Span<T> puede encapsular una matriz completa, como hizo en los ejemplos de la sección Memoria . Dado que admite la segmentación, Span<T> también puede apuntar a cualquier intervalo contiguo dentro de la matriz.

En el ejemplo siguiente se crea un segmento de los cinco elementos intermedios de una matriz de enteros de 10 elementos. Tenga en cuenta que el código duplica los valores de cada entero del segmento. Como se muestra en la salida, los cambios realizados por el intervalo se reflejan en los valores de la matriz.

using System;

var array = new int[] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
var slice = new Span<int>(array, 2, 5);
for (int ctr = 0; ctr < slice.Length; ctr++)
    slice[ctr] *= 2;

// Examine the original array values.
foreach (var value in array)
    Console.Write($"{value}  ");
Console.WriteLine();

// The example displays the following output:
//      2  4  12  16  20  24  28  16  18  20
module Program

open System

[<EntryPoint>]
let main _ =
    let array = [| 2; 4; 6; 8; 10; 12; 14; 16; 18; 20 |]
    let slice = Span<int>(array, 2, 5)
    for i = 0 to slice.Length - 1 do
        slice[i] <- slice[i] * 2

    // Examine the original array values.
    for value in array do
        printf $"{value}  "
    printfn ""
    0
// The example displays the following output:
//      2  4  12  16  20  24  28  16  18  20

Segmentos

Span<T> incluye dos sobrecargas del Slice método que forman un segmento fuera del intervalo actual que comienza en un índice especificado. Esto permite tratar los datos de como Span<T> un conjunto de fragmentos lógicos que se pueden procesar según sea necesario por partes de una canalización de procesamiento de datos con un impacto mínimo en el rendimiento. Por ejemplo, dado que los protocolos de servidor modernos suelen basarse en texto, la manipulación de cadenas y subcadenas es especialmente importante. En la String clase , el método principal para extraer subcadenas es Substring. En el caso de las canalizaciones de datos que dependen de una manipulación extensa de cadenas, su uso ofrece algunas penalizaciones de rendimiento, ya que:

  1. Crea una nueva cadena para contener la subcadena.
  2. Copia un subconjunto de los caracteres de la cadena original a la nueva cadena.

Esta operación de asignación y copia se puede eliminar mediante Span<T> o ReadOnlySpan<T>, como se muestra en el ejemplo siguiente:

using System;

class Program2
{
    static void Run()
    {
        string contentLength = "Content-Length: 132";
        var length = GetContentLength(contentLength.ToCharArray());
        Console.WriteLine($"Content length: {length}");
    }

    private static int GetContentLength(ReadOnlySpan<char> span)
    {
        var slice = span.Slice(16);
        return int.Parse(slice);
    }
}
// Output:
//      Content length: 132
module Program2

open System

let getContentLength (span: ReadOnlySpan<char>) =
    let slice = span.Slice 16
    Int32.Parse slice

let contentLength = "Content-Length: 132"
let length = getContentLength (contentLength.ToCharArray())
printfn $"Content length: {length}"
// Output:
//      Content length: 132